/*
 * Copyright 2010-2025 Hyland Software, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.activiti.examples.bpmn.executionlistener;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.activiti.engine.impl.test.PluggableActivitiTestCase;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.test.Deployment;
import org.activiti.examples.bpmn.executionlistener.CurrentActivityExecutionListener.CurrentActivity;
import org.activiti.examples.bpmn.executionlistener.RecorderExecutionListener.RecordedEvent;

/**

 */
public class ExecutionListenerTest extends PluggableActivitiTestCase {

    @Deployment(resources = { "org/activiti/examples/bpmn/executionlistener/ExecutionListenersProcess.bpmn20.xml" })
    public void testExecutionListenersOnAllPossibleElements() {
        RecorderExecutionListener.clear();

        // Process start executionListener will have executionListener class
        // that sets 2 variables
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
            "executionListenersProcess",
            "businessKey123"
        );

        String varSetInExecutionListener = (String) runtimeService.getVariable(
            processInstance.getId(),
            "variableSetInExecutionListener"
        );
        assertThat(varSetInExecutionListener).isNotNull();
        assertThat(varSetInExecutionListener).isEqualTo("firstValue");

        // Check if business key was available in execution listener
        String businessKey = (String) runtimeService.getVariable(processInstance.getId(), "businessKeyInExecution");
        assertThat(businessKey).isNotNull();
        assertThat(businessKey).isEqualTo("businessKey123");

        // Transition take executionListener will set 2 variables
        Task task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
        assertThat(task).isNotNull();
        taskService.complete(task.getId());

        varSetInExecutionListener = (String) runtimeService.getVariable(
            processInstance.getId(),
            "variableSetInExecutionListener"
        );

        assertThat(varSetInExecutionListener).isNotNull();
        assertThat(varSetInExecutionListener).isEqualTo("secondValue");

        ExampleExecutionListenerPojo myPojo = new ExampleExecutionListenerPojo();
        runtimeService.setVariable(processInstance.getId(), "myPojo", myPojo);

        task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
        assertThat(task).isNotNull();
        taskService.complete(task.getId());

        // First usertask uses a method-expression as executionListener:
        // ${myPojo.myMethod(execution.eventName)}
        ExampleExecutionListenerPojo pojoVariable = (ExampleExecutionListenerPojo) runtimeService.getVariable(
            processInstance.getId(),
            "myPojo"
        );
        assertThat(pojoVariable.getReceivedEventName()).isNotNull();
        assertThat(pojoVariable.getReceivedEventName()).isEqualTo("end");

        task = taskService.createTaskQuery().processInstanceId(processInstance.getId()).singleResult();
        assertThat(task).isNotNull();
        taskService.complete(task.getId());

        assertProcessEnded(processInstance.getId());

        List<RecordedEvent> events = RecorderExecutionListener.getRecordedEvents();
        assertThat(events).hasSize(1);
        RecordedEvent event = events.get(0);
        assertThat(event.getParameter()).isEqualTo("End Process Listener");
    }

    @Deployment(
        resources = { "org/activiti/examples/bpmn/executionlistener/ExecutionListenersStartEndEvent.bpmn20.xml" }
    )
    public void testExecutionListenersOnStartEndEvents() {
        RecorderExecutionListener.clear();

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess");
        assertProcessEnded(processInstance.getId());

        List<RecordedEvent> recordedEvents = RecorderExecutionListener.getRecordedEvents();
        assertThat(recordedEvents).hasSize(4);

        assertThat(recordedEvents.get(0).getActivityId()).isEqualTo("theStart");
        assertThat(recordedEvents.get(0).getActivityName()).isEqualTo("Start Event");
        assertThat(recordedEvents.get(0).getParameter()).isEqualTo("Start Event Listener");
        assertThat(recordedEvents.get(0).getEventName()).isEqualTo("end");

        assertThat(recordedEvents.get(1).getActivityId()).isEqualTo("noneEvent");
        assertThat(recordedEvents.get(1).getActivityName()).isEqualTo("None Event");
        assertThat(recordedEvents.get(1).getParameter()).isEqualTo("Intermediate Catch Event Listener");
        assertThat(recordedEvents.get(1).getEventName()).isEqualTo("end");

        assertThat(recordedEvents.get(2).getActivityId()).isEqualTo("signalEvent");
        assertThat(recordedEvents.get(2).getActivityName()).isEqualTo("Signal Event");
        assertThat(recordedEvents.get(2).getParameter()).isEqualTo("Intermediate Throw Event Listener");
        assertThat(recordedEvents.get(2).getEventName()).isEqualTo("start");

        assertThat(recordedEvents.get(3).getActivityId()).isEqualTo("theEnd");
        assertThat(recordedEvents.get(3).getActivityName()).isEqualTo("End Event");
        assertThat(recordedEvents.get(3).getParameter()).isEqualTo("End Event Listener");
        assertThat(recordedEvents.get(3).getEventName()).isEqualTo("start");
    }

    @Deployment(
        resources = {
            "org/activiti/examples/bpmn/executionlistener/ExecutionListenersFieldInjectionProcess.bpmn20.xml",
        }
    )
    public void testExecutionListenerFieldInjection() {
        Map<String, Object> variables = new HashMap<String, Object>();
        variables.put("myVar", "listening!");

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(
            "executionListenersProcess",
            variables
        );

        Object varSetByListener = runtimeService.getVariable(processInstance.getId(), "var");
        assertThat(varSetByListener).isNotNull();
        assertThat(varSetByListener).isInstanceOf(String.class);

        // Result is a concatenation of fixed injected field and injected expression
        assertThat(varSetByListener).isEqualTo("Yes, I am listening!");
    }

    @Deployment(
        resources = { "org/activiti/examples/bpmn/executionlistener/ExecutionListenersCurrentActivity.bpmn20.xml" }
    )
    public void testExecutionListenerCurrentActivity() {
        CurrentActivityExecutionListener.clear();

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess");
        assertProcessEnded(processInstance.getId());

        List<CurrentActivity> currentActivities = CurrentActivityExecutionListener.getCurrentActivities();
        assertThat(currentActivities).hasSize(3);

        assertThat(currentActivities.get(0).getActivityId()).isEqualTo("theStart");
        assertThat(currentActivities.get(0).getActivityName()).isEqualTo("Start Event");

        assertThat(currentActivities.get(1).getActivityId()).isEqualTo("noneEvent");
        assertThat(currentActivities.get(1).getActivityName()).isEqualTo("None Event");

        assertThat(currentActivities.get(2).getActivityId()).isEqualTo("theEnd");
        assertThat(currentActivities.get(2).getActivityName()).isEqualTo("End Event");
    }

    @Deployment(
        resources = {
            "org/activiti/examples/bpmn/executionlistener/ExecutionListenersForSubprocessStartEndEvent.bpmn20.xml",
        }
    )
    public void testExecutionListenersForSubprocessStartEndEvents() {
        RecorderExecutionListener.clear();

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess");

        List<RecordedEvent> recordedEvents = RecorderExecutionListener.getRecordedEvents();
        assertThat(recordedEvents).hasSize(1);
        assertThat(recordedEvents.get(0).getParameter()).isEqualTo("Process Start");

        RecorderExecutionListener.clear();

        Task task = taskService.createTaskQuery().singleResult();
        taskService.complete(task.getId());

        assertProcessEnded(processInstance.getId());

        recordedEvents = RecorderExecutionListener.getRecordedEvents();

        assertThat(recordedEvents).hasSize(3);
        assertThat(recordedEvents.get(0).getParameter()).isEqualTo("Subprocess Start");
        assertThat(recordedEvents.get(1).getParameter()).isEqualTo("Subprocess End");
        assertThat(recordedEvents.get(2).getParameter()).isEqualTo("Process End");
    }

    @Deployment(
        resources = {
            "org/activiti/examples/bpmn/executionlistener/ExecutionListenersOnProcessEndWhenProcessIsDeleted.bpmn20.xml",
        }
    )
    public void testExecutionListenerOnProcessEndWhenProcessIsDeleted() {
        RecorderExecutionListener.clear();

        // GIVEN: a process with an executionListener to be executed at the end of the process
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("executionListenersProcess");

        // WHEN: we delete the process
        runtimeService.deleteProcessInstance(processInstance.getProcessInstanceId(), null);

        // THEN: the process should end and
        assertProcessEnded(processInstance.getId());

        // THEN: and the executionListener should have run
        List<RecordedEvent> recordedEvents = RecorderExecutionListener.getRecordedEvents();
        assertThat(recordedEvents).hasSize(1);
        assertThat(recordedEvents.get(0).getParameter()).isEqualTo("Process has ended");
    }
}
